<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>正则表达式的构建</title>
</head>

<body>
    <script>
        /* 总结：
            1.平衡法则：
                ->匹配预期的字符串
                ->不匹配非预期的字符串
                ->可读性和可维护性
                ->效率
            2.构建正则前提
                ->是否能使用正则？
                ->是否有必要使用正则？
                ->是否有必要构建一个复杂的正则？
            3.准确性：
                所谓准确性，就是能匹配预期的目标，并且不匹配非预期的目标。这里提到了“预期”二字，那么我们就需要知道目标的组成规则。
            4.效率
                ->正则表达式的运行分为如下的阶段：
                    ①编译；
                    ②设定起始位置；
                    ③尝试匹配；
                    ④匹配失败的话，从下一位开始继续第 ③ 步；
                    ⑤最终结果：匹配成功或失败
                ->提高效率：
                    -使用具体型字符组来代替通配符（减少回溯）
                        举例：123"abc"123  /".*"/贪婪匹配 --> /".*?"/ 非贪婪匹配 --> /"[^"]*"/ 使用具体字符
                    -使用非捕获型分组(减少内存的消耗)
                        举例：/^[+-]?(\d\.\d+|\d+|\.\d+)$/g  -->  /^[+-]?(?:\d\.\d+|\d+|\.\d+)$/g
                            1.23、+1.23、-1.23
                            10、+10、-10
                            .2、+.2、-.2
                    -独立出确定字符
                        举例：/a{1,}/ --> /aa{0,}/ 由a{1,}得出匹配中必有一个a字符
                    -提取分支公共部分
                        举例：/this|that/修改成 /th(?:is|at)/
                    -减少分支的数量，缩小它们的范围
                        举例:/red|read/可以修改成 /rea?d/
            ->补充：
                str.search
                str.substring
                str.join
                str.split
        */
        //构建则前提
        //-1是否能使用正则？
        //个人思路：本来以为正则对象中可以使用变量，之后通过 循环 + 更改reg中对0的匹配个数 + reg.test 将每部分匹配出来（reg.lastindex属性会跟随匹配位置变化）
        //现实：正则对象中不可以使用变量
        /* var str = "10100100010000100000";
        var i = 1, j =2;
        var reg = /\d{i,j}/;
        console.log(reg.test(12)); */
        //-2是否有必要使用正则？
        //提取日期2020-10-27
        //正则提取
        console.log("2020-10-27".match(
        /(\d{4})-(\d{2})-(\d{2})/)); //["2020-10-27", "2020", "10", "27", index: 0, input: "2020-10-27", groups: undefined]
        //str.split提取
        console.log("2020-10-27".split("-")); //["2020", "10", "27"]
        //判断是否有问号
        //正则
        var str = "?id=xx&act=search";
        console.log(str.search(/\?/)); //索引：0
        //str.indexOf
        console.log(str.indexOf('?')); //0
        //获取子串
        //正则
        var str = "JavaScript";
        console.log(str.match(/.{4}(.+)/)); //["JavaScript", "Script", index: 0, input: "JavaScript", groups: undefined]
        //str.sunstring
        console.log(str.substring(4)); //Script
        //-3是否有必要构建一个复杂的正则
        //密码匹配：要求密码长度 6-12 位，由数字、小写字符和大写字母组成，但必须至少包括2种字符。
        console.log("密码匹配1.0：");
        var reg = /((?=.*[0-9])(?=.*[a-z])|(?=.*[0-9])(?=.*[A-Z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9a-zA-Z]{6,12}$/;
        var reg = /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9a-zA-Z]{6,12}$/;
        console.log(reg.test(1234567)); // false
        console.log(reg.test("abcdef")); // false 全是小写字母
        console.log(reg.test("ABCDEFGH")); // false 全是大写字母
        console.log(reg.test("ab23C")); // false 不足6位
        console.log(reg.test("ABCDEF234")); // true 大写字母和数字
        console.log(reg.test("abcdEF234")); // true 三者都有

        //以使用多个小正则来做
        console.log("密码匹配2.0：");
        var regex1 = /^[0-9A-Za-z]{6,12}$/;//所有情况
        var regex2 = /^[0-9]{6,12}$/;//全是数字的情况
        var regex3 = /^[A-Z]{6,12}$/;//全是大写字母
        var regex4 = /^[a-z]{6,12}$/;//全是小写字母
        function checkPassword(string) {
            if (!regex1.test(string)) return false;
            if (regex2.test(string)) return false;
            if (regex3.test(string)) return false;
            if (regex4.test(string)) return false;
            return true;
        }
        console.log(checkPassword(1234567)); // false
        console.log(checkPassword("abcdef")); // false 全是小写字母
        console.log(checkPassword("ABCDEFGH")); // false 全是大写字母
        console.log(checkPassword("ab23C")); // false 不足6位
        console.log(checkPassword("ABCDEF234")); // true 大写字母和数字
        console.log(checkPassword("abcdEF234")); // true 三者都有

        //准确性
        //-1匹配固定电话
        /* 目标字符串：
            055188888888      
            0551-88888888
            (0551)88888888
           规则：
            ->区号是 "0" 开头的 3 到 4 位数字，   0\d{3,4}
            ->号码是非 "0" 开头的 7 到 8 位数字   [1-9]\d[6,7]
        */
        console.log("准确性：");
        console.log("匹配固定电话：");
        var reg = /^(0\d{3,4}[1-9]\d{6,7}|0\d{3,4}-[1-9]\d{6,7}|\(0\d{3,4}\)[1-9]\d{6,7})$/;
        var reg = /^(0\d{3,4}|0\d{3,4}-|\(0\d{3,4}\))[1-9]\d{6,7}$/;
        var reg = /^(0\d{3,4}-?|\(0\d{3,4}\))[1-9]\d{6,7}$/
        // var reg = /^(0\d{3,4}-?|\((\1)\))[1-9]\d{6,7}$/--分支内的引用问题
        var str1 = "055188888888";
        var str2 = "0551-88888888";
        var str3 = "(0551)88888888";
        console.log(reg.test(str1));//true
        console.log(reg.test(str2));//true
        console.log(reg.test(str3));//true
        /* console.log(reg.exec(str1));
        console.log(RegExp.$1);
        console.log(RegExp.$2);
        console.log(RegExp.$3);
        console.log(RegExp.$4);
        console.log(RegExp.$5); */
        //-2匹配浮点数
        /* 举例：
            1.23、+1.23、-1.23
            10、+10、-10
            .2、+.2、-.2
         */
        console.log("匹配浮点数：");
        var reg = /^[+-]?[0-9]\.\d{2}$/;
        console.log(reg.test("1.23"));
        console.log(reg.test("-1.23"));
        console.log(reg.test("+1.23"));
        var reg = /^[+-]?\d{2}$/;
        console.log(reg.test("10"));
        console.log(reg.test("-10"));
        console.log(reg.test("+10"));
        var reg = /^[+-]?\.\d$/;
        console.log(reg.test(".2"));
        console.log(reg.test("+.2"));
        console.log(reg.test("-.2"));
        var reg = /([+-]?\d?\.?\d+)/g;
        console.log("1.23,+1.23,-1.23,10,-10,+10,.2,-.2,+.2".match(reg));//["1.23", "+1.23", "-1.23", "10", "-10", "+10", ".2", "-.2", "+.2"]

        //效率
        /* 
                -使用具体型字符组来代替通配符（减少回溯）
                    举例：123"abc"123  /".*"/贪婪匹配 --> /".*?"/ 非贪婪匹配 --> /"[^"]*"/ 使用具体字符

                -使用非捕获型分组(减少内存的消耗)
                    举例：/^[+-]?(\d\.\d+|\d+|\.\d+)$/g  -->  /^[+-]?(?:\d\.\d+|\d+|\.\d+)$/g
                        1.23、+1.23、-1.23
                        10、+10、-10
                        .2、+.2、-.2

                -独立出确定字符
                    举例：/a{1,}/ --> /aa{0,}/ 由a{1,}得出匹配中必有一个a字符

                -提取分支公共部分
                   举例：/this|that/修改成 /th(?:is|at)/

                -减少分支的数量，缩小它们的范围
                    /red|read/可以修改成 /rea?d/
        */
        console.log("效率：");
        //使用具体性字符来代替通配符
        //举例： 123"abc"123 --> "abc"
        console.log("使用具体性字符来代替通配符：");
        var str = "\"aaa\"";
        // for(var i = 0;i<10e5;i++){
        for(var i = 0;i<10e2;i++){
            str += 2222222222222;
        }
        // var reg = /".*"/g;//57.5
        // var reg = /".*?"/g;// 27.5
        //var reg = /"[^"]*"/g;//29.1
        console.time("reg运行时长");
        console.log(str.match(reg));//["aaa"]
        console.timeEnd("reg运行时长");
        //使用非捕获型分组
        /* 举例：
            1.23、+1.23、-1.23
            10、+10、-10
            .2、+.2、-.2
        */
        console.log("使用非捕获分组：");
        var reg = /^[+-]?(\d\.\d+|\d+|\.\d+)$/g;
        console.log(reg.test('+.2'));//true
        var reg = /^[+-]?(?:\d\.\d+|\d+|\.\d+)$/g;
        console.log(reg.test('+.2'));//true   

        /* 方法的补充：
            ->str.search
                -作用：执行正则表达式和 String 对象之间的一个搜索匹配。
                -参数:正则表达式或非正则对象（会通过new RegExp的方式转换为正则对象）
                    全局与非全局匹配相同，均返回第一个匹配项的索引
                -返回值：匹配成功，则 search() 返回正则表达式在字符串中首次匹配项的索引;否则，返回 -1。
            str.substring
                ->作用：返回一个字符串在开始索引到结束索引之间的一个子集, 或从开始索引直到字符串的末尾的一个子集
                ->参数：indexStart ，indexEnd-（可选，且不包含indexEnd处的字符）
                    如果 indexStart 等于 indexEnd，substring 返回一个空字符串。（2,2）
                    如果省略 indexEnd，substring 提取字符一直到字符串末尾。（2，）
                    如果任一参数小于 0 或为 NaN，则被当作 0。（-1，）
                    如果任一参数大于 stringName.length，则被当作 stringName.length。
                    如果 indexStart 大于 indexEnd，则 substring 的执行效果就像两个参数调换了一样。
                ->返回值：包含给定字符串的指定部分的新字符串。
            arr.join
                ->作用：
                    -将一个数组（或一个类数组对象）的所有元素连接成一个字符串并返回这个字符串。
                    -如果数组只有一个项目，那么将返回该项目而不使用分隔符。
                ->参数：（分割符） 可选
                    -如果需要，将分隔符转换为字符串。
                    -如果缺省该值，数组元素用逗号（,）分隔。
                    -如果separator是空字符串("")，则所有元素之间都没有任何字符。
                ->返回值：一个所有数组元素连接的字符串。如果 arr.length 为0，则返回空字符串。
            str.split
                ->作用：使用指定的分隔符字符串将一个String对象分割成子字符串数组，以一个指定的分割字串来决定每个拆分的位置。 
                ->参数：separator，limit
                    -描述:找到分隔符后，将其从字符串中删除，并将子字符串的数组返回。
                    -separator：
                        一个字符串或正则表达式
                        如果为缺省，返回整个字符串组成的数组
                        如果在str中省略或不出现分隔符，则返回的数组包含一个由整个字符串组成的元素。
                        如果纯文本分隔符包含多个字符，则必须找到整个字符串来表示分割点。
                        如果分隔符为空字符串，则将str原字符串中每个字符的数组形式返回。
                        如果分隔符出现在字符串的开始或结尾，或两者都分开，分别以空字符串开头，结尾或两者开始和结束。
                        如果字符串仅由一个分隔符实例组成，则该数组由两个空字符串组成。
                        当字符串为""时，split（）返回一个包含一个空字符串的数组，而不是一个空数组，如果字符串和分隔符都是""，则返回一个空数组。

                        如果分隔符是包含捕获括号的正则表达式，则每次分隔符匹配时，捕获括号的结果（包括任何未定义的结果）将被拼接到输出数组中。但是，并不是所有浏览器都支持此功能。

                        特别的--使用数组:['a','b'] 等价于 String(['a','b'])  =  "a,b"
                    -limit：
                        一个整数，限定返回的分割片段数量
                        字符串没分割完就达到limit了,返回limit数目分割串的数组
                        字符串分割完了但还没达到limit，返回所有分割串的数组
                -返回值：一个数组
        */
        //search方法
        console.log("------search方法：");
        var reg = /\d/;
        var str = "a1a2";
        console.log(str.search(reg));//索引 1
        var reg = /\d/g;
        var str = "a1a2";
        console.log(str.search(reg));//索引 1
        //substring方法
        console.log("------substring方法：");
        var str = "abcdef";
        console.log(str.substring(1,2));//b
        console.log(str.substring(1,1));//""
        console.log(str.substring(2,));//cdef
        console.log(str.substring(NaN,4));//abcd
        console.log(str.substring(-10,4));//abcd
        console.log(str.substring(2,10));//cdef
        console.log(str.substring(5,2));//cde
        //arr.join方法
        console.log("------arr.join方法：");
        var arr = [1,2,3];
        console.log(arr.join(":"));//a:b:c
        console.log(arr.join());//a,b,c
        console.log(arr.join(""));//abc
        var arr = [1];
        console.log(arr.join(":"));//a
        console.log(arr.join());//a
        console.log(arr.join(""));//a
        var arr = [];
        console.log(arr.join(":"));//""
        console.log(arr.join());//""
        console.log(arr.join(""));//""
        //str.split方法
        console.log("------str.split方法:");
        var str = "abc,.abc,.abc";
        console.log(str.split(',.'));//["abc", "abc", "abc"]
        console.log(str.split(' '));//["abc,.abc,.abc"]
        console.log(str.split(''));//["a", "b", "c", ",", ".", "a", "b", "c", ",", ".", "a", "b", "c"]
        console.log(str.split('',3));//["a", "b", "c"]
        console.log(str.split('',20));//["a", "b", "c", ",", ".", "a", "b", "c", ",", ".", "a", "b", "c"]
        console.log(str.split());//["abc,.abc,.abc"]
        console.log(str.split('a'));//["", "bc,.", "bc,.", "bc"]
        var str = "";
        console.log(str.split());//[""]
        var str = "";
        console.log(str.split(""));//[]
        var str = "a";
        console.log(str.split("a"));//["", ""]
        var str = "a1b2.ce";
        console.log(str.split(/\d\./));//["a1b", "ce"]
        console.log(str.split(/(\d)\./));//["a1b","2","ce"]

        var str = "ca,bc,a,bca,bca,bc";
        var res = str.split(['a','b']);//split(['a','b']) <=> split('a,b')
        var res = str.split("a,b");
        console.log(res);  //["c", "c,", "c", "c", "c"]
    </script>
</body>

</html>